package util

import (
	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/pem"
	"fmt"
	"github.com/nuttech/bell"
	"io/ioutil"
	"math/big"
	"net"
	"os"
	"path/filepath"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
)

// TempCerts for temporary certificates
type TempCerts struct {
	certsDirectory string
	certFileName   string
	keyFileName    string
}

// GenerateTestCertificates generates test certificates
func GenerateTestCertificates() (*TempCerts, error) {
	// Source - https://golang.org/src/crypto/tls/generate_cert.go

	var err error
	tempDir, err := ioutil.TempDir("", "test-certs")
	if err != nil {
		return nil, fmt.Errorf("failed to create a temp directory: %s", err)
	}

	certFileName := filepath.Join(tempDir, "cert.pem")
	keyFileName := filepath.Join(tempDir, "key.pem")

	var privateKey *rsa.PrivateKey
	privateKey, err = rsa.GenerateKey(rand.Reader, 2048)

	if err != nil {
		return nil, fmt.Errorf("failed to generate private key: %s", err)
	}

	notBefore := time.Now()
	notAfter := notBefore.Add(365 * 24 * time.Hour)

	serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
	serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)

	if err != nil {
		return nil, fmt.Errorf("failed to generate serial number: %s", err)
	}

	template := x509.Certificate{
		SerialNumber: serialNumber,
		Subject: pkix.Name{
			Organization: []string{"synapse"},
		},
		NotBefore: notBefore,
		NotAfter:  notAfter,

		KeyUsage:              x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
		BasicConstraintsValid: true,
		IPAddresses:           []net.IP{net.IPv4(127, 0, 0, 1)},
		DNSNames:              []string{"localhost"},
	}

	publicKey := &privateKey.PublicKey
	derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, publicKey, privateKey)
	if err != nil {
		return nil, fmt.Errorf("Failed to create certificate: %s", err)
	}

	err = writePemBlockToFile(certFileName, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
	if err != nil {
		return nil, err
	}

	err = writePemBlockToFile(keyFileName, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(privateKey)})
	if err != nil {
		return nil, err
	}

	return &TempCerts{
		certsDirectory: tempDir,
		certFileName:   certFileName,
		keyFileName:    keyFileName,
	}, nil
}

func writePemBlockToFile(fileName string, pemBlock *pem.Block) error {
	certOut, err := os.Create(fileName)
	if err != nil {
		return fmt.Errorf("failed to create %s: %s", fileName, err)
	}

	if err := pem.Encode(certOut, pemBlock); err != nil {
		return fmt.Errorf("failed to write block to file: %s", err)
	}

	if err := certOut.Close(); err != nil {
		return fmt.Errorf("unable to close %s: %s", fileName, err)
	}

	return nil
}

func TestGlobalInit(t *testing.T) {
	os.Setenv("KBLT_PORT_MAIN", "9998")
	os.Setenv("KBLT_PORT_LOG", "9999")
	os.Setenv("KBLT_PREFIX_EXCEPTION", "true")
	assert.NotPanics(t, func() {
		GlobalInit()
	}) //All it's ok

	os.Setenv("KBLT_PORT_MAIN", "9998")
	os.Setenv("KBLT_PORT_LOG", "9999")
	os.Setenv("KBLT_PREFIX_EXCEPTION", "myexcept")
	assert.NotPanics(t, func() {
		GlobalInit()
	}) //All it's ok

	os.Setenv("KBLT_PORT_MAIN", "")
	os.Setenv("KBLT_PORT_LOG", "")
	os.Setenv("KBLT_PREFIX_EXCEPTION", "")
	GlobalInit() //Bad port
}

func TestIsFullRole(t *testing.T) {
	tests := []struct {
		name string
		f    func()
		def  func()
		want bool
	}{
		{
			name: "full role",
			f: func() {
				KbltMutator = "true"
				KbltValidator = "true"
			},
			def: func() {
				KbltMutator = "false"
				KbltValidator = "false"
			},
			want: true,
		},
		{
			name: "not full role 1",
			f: func() {
				KbltMutator = "true"
				KbltValidator = "false"
			},
			def: func() {
				KbltMutator = "false"
				KbltValidator = "false"
			},
			want: false,
		},
		{
			name: "not full role 2",
			f: func() {
				KbltMutator = "false"
				KbltValidator = "true"
			},
			def: func() {
				KbltMutator = "false"
				KbltValidator = "false"
			},
			want: false,
		},
		{
			name: "not full role 3",
			f: func() {
				KbltMutator = "false"
				KbltValidator = "false"
			},
			def: func() {
				KbltMutator = "false"
				KbltValidator = "false"
			},
			want: false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			tt.f()
			defer tt.def()
			assert.Equalf(t, tt.want, IsFullRole(), "IsFullRole()")
		})
	}
}

func TestSendEvent(t *testing.T) {
	type args struct {
		eventName string
		value     interface{}
	}
	tests := []struct {
		name         string
		args         args
		shouldBeSent bool
	}{
		{
			name: "ok",
			args: args{
				eventName: "someEventName",
				value:     "WOWvalue",
			},
			shouldBeSent: true,
		},
		{
			name: "ok",
			args: args{
				eventName: "someEventName",
				value:     "WOWvalue",
			},
			shouldBeSent: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if tt.shouldBeSent {
				bell.Listen(tt.args.eventName, func(message bell.Message) {})
			}
			if !tt.shouldBeSent {
				bell.Listen("other", func(message bell.Message) {
					t.Error("the message should not have been sent")
				})
			}
			SendEvent(tt.args.eventName, tt.args.value)
			err := SendEventWithErr(tt.args.eventName, tt.args.value)
			if err != nil {
				t.Errorf("unexpectrd err %v", err)
			}
		})
	}
	t.Run("not set listener", func(t *testing.T) {
		SendEvent("event", "val")
		err := SendEventWithErr("event", "val")
		if err == nil {
			t.Errorf("unexpectrd nill err %v", err)
		}
	})
}

func TestMergeStruct(t *testing.T) {
	type args struct {
		aT map[string]interface{}
		aS map[string]interface{}
	}
	tests := []struct {
		name string
		args args
		want map[string]interface{}
	}{
		{
			name: "case 1",
			args: args{
				aT: map[string]interface{}{
					"data": map[string]interface{}{
						"1": "val1",
						"2": []string{
							"2", "2",
						},
						"5": []interface{}{1, 2, 3},
					},
				},
				aS: map[string]interface{}{
					"data": map[string]interface{}{
						"3": "val3",
						"4": "val4",
						"5": []interface{}{4, 5, 6},
					},
				},
			},
			want: map[string]interface{}{
				"data": map[string]interface{}{
					"1": "val1",
					"2": []string{"2", "2"},
					"3": "val3",
					"4": "val4",
					"5": []interface{}{1, 2, 3, 4, 5, 6},
				},
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			assert.Equalf(t, tt.want, MergeStruct(tt.args.aT, tt.args.aS, true), "MergeStruct(%v, %v)", tt.args.aT, tt.args.aS)
		})
	}
}

func TestMergeSlice(t *testing.T) {
	type args struct {
		aT []interface{}
		aS []interface{}
	}
	tests := []struct {
		name string
		args args
		want []interface{}
	}{
		{
			name: "case_1",
			args: args{
				aT: []interface{}{
					map[string]interface{}{
						"name": "pompom",
					},
				},
				aS: []interface{}{
					map[string]interface{}{
						"name": "pompompo",
					}},
			},
			want: []interface{}{
				map[string]interface{}{"name": "pompom"},
				map[string]interface{}{"name": "pompompo"}},
		},
		{
			name: "case_2_same_values",
			args: args{
				aT: []interface{}{
					map[string]interface{}{
						"name": "pompom",
					},
				},
				aS: []interface{}{
					map[string]interface{}{
						"name": "pompom",
					}},
			},
			want: []interface{}{
				map[string]interface{}{"name": "pompom"}},
		},
		{
			name: "case_3_no_name",
			args: args{
				aT: []interface{}{
					map[string]interface{}{
						"momomo": "pompom",
					},
				},
				aS: []interface{}{
					map[string]interface{}{
						"momomo": "pompom",
					}},
			},
			want: []interface{}{
				map[string]interface{}{"momomo": "pompom"},
				map[string]interface{}{"momomo": "pompom"}},
		},
		{
			name: "case_3_no_map",
			args: args{
				aT: []interface{}{1},
				aS: []interface{}{1},
			},
			want: []interface{}{1, 1},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			assert.Equalf(t, tt.want, MergeSlice(tt.args.aT, tt.args.aS), "MergeSlice(%v, %v)", tt.args.aT, tt.args.aS)
		})
	}
}
